package osami.communication.translate.json;

import java.util.Vector;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import osami.bundle.OsamiServiceReference;
import osami.communication.OsamiIdentifier;
import osami.communication.translate.FindServiceMessage;
import osami.communication.translate.FindServiceResponseMessage;
import osami.communication.translate.HandshakeMessage;
import osami.communication.translate.Message;
import osami.communication.translate.MulticastJoinMessage;
import osami.communication.translate.RemoteCallMessage;
import osami.communication.translate.RemoteCallResponseMessage;
import osami.communication.translate.ServiceUpdateMessage;
import osami.communication.translate.TranslationException;
import osami.communication.translate.Translator;

/**
 * Translates OSAMI messages into JSON strings
 * 
 * @author osami
 *
 */
public class JSONTranslator extends Translator
{
	public static final String MESSAGE_TYPE = 			"MT";
	public static final String CALL_NUMBER = 			"CN";
	public static final String SERVICES = 				"SERVICES";
	public static final String SERVICE_NAME =			"SNAME";
	public static final String SERVICE_INTERFACES =		"SIFACES";
	public static final String SUPPORTED_TRANSLATORS =	"TR";
	public static final String ID = 					"ID";
	public static final String INTERFACE_NAME = 		"IN";
	public static final String METHOD_NAME = 			"MN";
	public static final String METHOD_PARAMETERS = 		"MP";
	public static final String ROUTE_TO_SERVICE =		"RT";
	public static final String CALL_PATH =				"CP";
	public static final String RETURN_VALUE = 			"RV";
	public static final String HOP_COUNTER =			"HOPS";

	public static final String SERVICE_UPDATE_ACTION = 	"SUA";
	public static final String MULTICAST_ACTION = 		"MA";
	public static final String IS_SUCCESS =				"IS";
	
	
	public JSONTranslator()
	{
		super( "json" );
	}
	
	public Message translate( String aMsg ) throws TranslationException
	{
		System.out.println( "JSONTranslator.translate( \"" + aMsg + "\" ) str->msg" );
		
		if ( aMsg == null )
			throw new TranslationException( "ERROR: null message received." );

		try
		{
			// create a JSON message from the string
			JSONObject jsonMessage = new JSONObject( aMsg );
			
			// get message's type
			int messageType = jsonMessage.getInt( MESSAGE_TYPE );
			
			switch ( messageType )
			{
			case Message.FIND_SERVICE:
			{
				FindServiceMessage message = new FindServiceMessage( jsonMessage.getInt( CALL_NUMBER ) );
				
				// set hop counter
				message.setHopCounter( jsonMessage.getInt( HOP_COUNTER ) );

				// set call path
				JSONArray callPathArray = jsonMessage.getJSONArray( CALL_PATH );
				Vector<OsamiIdentifier> callPath = new Vector<OsamiIdentifier>();
				for ( int i = 0; i < callPathArray.length(); i++ )
					callPath.add( new OsamiIdentifier( callPathArray.getInt(i) ) );
				message.setCallPath( callPath );
				
				// set interface name
				message.setInterfaceName( jsonMessage.getString( INTERFACE_NAME ) );
				
				return message;
			}
			case Message.FIND_SERVICE_RESPONSE:
			{
				FindServiceResponseMessage message = new FindServiceResponseMessage( jsonMessage.getInt( CALL_NUMBER ), jsonMessage.getBoolean( IS_SUCCESS ) );
				
				// the message only has the service reference if the "find service" -call was successful
				if ( message.isSuccess() )
					message.setServiceReference( JSONToServiceReference( jsonMessage.getJSONObject( SERVICES ) ) );
				
				return message;
			}
			case Message.MULTICAST_JOIN:
			{
				return new MulticastJoinMessage( jsonMessage.getInt( MULTICAST_ACTION ) );
			}
			case Message.REMOTE_CALL:
			{
				RemoteCallMessage message = new RemoteCallMessage( jsonMessage.getInt( CALL_NUMBER ),
						jsonMessage.getString( INTERFACE_NAME ),
						jsonMessage.getString( METHOD_NAME ) );

				// set parameters
				JSONArray parametersArray = jsonMessage.getJSONArray( METHOD_PARAMETERS );
				Vector<Object> parameters = new Vector<Object>();
				for ( int i = 0; i < parametersArray.length(); i++ )
					parameters.add( parametersArray.get(i) );
				message.setParameters( parameters );

				// set route
				JSONArray routeArray = jsonMessage.getJSONArray( ROUTE_TO_SERVICE );
				Vector<OsamiIdentifier> route = new Vector<OsamiIdentifier>();
				for ( int i = 0; i < routeArray.length(); i++ )
					route.add( new OsamiIdentifier( routeArray.getInt(i) ) );
				message.setRoute( route );

				// set call path
				JSONArray callPathArray = jsonMessage.getJSONArray( CALL_PATH );
				Vector<OsamiIdentifier> callPath = new Vector<OsamiIdentifier>();
				for ( int i = 0; i < callPathArray.length(); i++ )
					callPath.add( new OsamiIdentifier( callPathArray.getInt(i) ) );
				message.setCallPath( callPath );
				
				return message;
			}
			case Message.REMOTE_CALL_RESPONSE:
			{
				RemoteCallResponseMessage message = new RemoteCallResponseMessage( jsonMessage.get( RETURN_VALUE ) );
				message.setCallNumber( jsonMessage.getInt( CALL_NUMBER ) );
				return message;
			}
			case Message.SERVICE_UPDATE:
			{
				ServiceUpdateMessage message = new ServiceUpdateMessage();
				
				JSONArray servicesArray = jsonMessage.getJSONArray( SERVICES );

				Vector<OsamiServiceReference> serviceReferences = new Vector<OsamiServiceReference>();
				for ( int i = 0; i < servicesArray.length(); i++ )
					serviceReferences.add( JSONToServiceReference( servicesArray.getJSONObject(i) ) );

				message.setServices( serviceReferences );
				message.setSender( new OsamiIdentifier( jsonMessage.getInt( ID ) ) );
				
				message.setAction( jsonMessage.getInt( SERVICE_UPDATE_ACTION ) );
				
				return message;
			}
			case Message.HANDSHAKE:
			{
				HandshakeMessage message = new HandshakeMessage();
				
				JSONArray translatorNamesArray = jsonMessage.getJSONArray( SUPPORTED_TRANSLATORS );

				Vector<String> translatorNames = new Vector<String>();
				for ( int i = 0; i < translatorNamesArray.length(); i++ )
					translatorNames.add( translatorNamesArray.getString(i) );
				
				message.setTranslatorNames( translatorNames );

				message.setSender( new OsamiIdentifier( jsonMessage.getInt( ID ) ) );
				
				return message;
			}
			default: throw new JSONException( "UNKNOWN MESSAGE TYPE: " + messageType );
			}
		}
		catch ( JSONException aException )
		{
			aException.printStackTrace();
			throw new TranslationException( aException.getMessage() );
		}
	}

	public String translate( Message aMsg ) throws TranslationException
	{
		JSONObject jsonMessage = new JSONObject();
		System.out.println( "JSONTranslator.translate( \"" + aMsg.getClass().getName() + "\" ) msg->str" );

		try
		{
			// insert message type
			jsonMessage.put( MESSAGE_TYPE, aMsg.getType() );

			// add additional message information
			switch( aMsg.getType() )
			{
			case Message.FIND_SERVICE:
			{
				FindServiceMessage message = (FindServiceMessage)aMsg;

				// insert call number
				jsonMessage.put( CALL_NUMBER, message.getCallNumber() );
				
				// insert hop counter
				jsonMessage.put( HOP_COUNTER, message.getHopCounter() );

				// insert the call path 
				JSONArray callPath = new JSONArray();
				for ( OsamiIdentifier identifier : message.getCallPath() )
					callPath.put( identifier.getID() );
				jsonMessage.put( CALL_PATH, callPath );

				// insert interface name
				jsonMessage.put( INTERFACE_NAME, message.getInterfaceName() );
			}break;
			case Message.FIND_SERVICE_RESPONSE:
			{
				FindServiceResponseMessage message = (FindServiceResponseMessage)aMsg;

				// insert call number
				jsonMessage.put( CALL_NUMBER, message.getCallNumber() );
				
				// insert success value
				jsonMessage.put( IS_SUCCESS, message.isSuccess() );
				
				// insert remote service reference
				if ( message.getServiceReference() != null )
					jsonMessage.put( SERVICES, ServiceReferenceToJSON( message.getServiceReference() ) );
			}break;
			case Message.MULTICAST_JOIN:
			{
				MulticastJoinMessage message = (MulticastJoinMessage)aMsg;

				// insert action code
				jsonMessage.put( MULTICAST_ACTION, message.getAction() );
			}break;
			case Message.REMOTE_CALL:
			{
				RemoteCallMessage message = (RemoteCallMessage)aMsg;

				// insert call number
				jsonMessage.put( CALL_NUMBER, message.getCallNumber() );
				
				// insert interface & method names
				jsonMessage.put( INTERFACE_NAME, message.getInterfaceName() );
				jsonMessage.put( METHOD_NAME, message.getMethodName() );

				// insert method parameters
				JSONArray parameters = new JSONArray();
				for ( Object parameter : message.getParameters() )
					parameters.put( parameter );
				jsonMessage.put( METHOD_PARAMETERS, parameters );

				// insert route to the service
				JSONArray route = new JSONArray();
				for ( OsamiIdentifier identifier : message.getRoute() )
					route.put( identifier.getID() );
				jsonMessage.put( ROUTE_TO_SERVICE, route );

				// insert the call path 
				JSONArray callPath = new JSONArray();
				for ( OsamiIdentifier identifier : message.getCallPath() )
					callPath.put( identifier.getID() );
				jsonMessage.put( CALL_PATH, callPath );
			}break;
			case Message.REMOTE_CALL_RESPONSE:
			{
				RemoteCallResponseMessage message = (RemoteCallResponseMessage)aMsg;

				jsonMessage.put( RETURN_VALUE, message.getReturnValue() );
				jsonMessage.put( CALL_NUMBER, message.getCallNumber() );
			}break;
			case Message.SERVICE_UPDATE:
			{
				ServiceUpdateMessage message = (ServiceUpdateMessage)aMsg;

				jsonMessage.put( SERVICE_UPDATE_ACTION, message.getAction() );

				JSONArray services = new JSONArray();
				for ( int i = 0; i < message.getServices().size(); i++ )
					services.put( ServiceReferenceToJSON( message.getServices().elementAt(i) ) );
				
				jsonMessage.put( SERVICES, services );
				jsonMessage.put( ID, message.getSender().getID() );
			}break;
			case Message.HANDSHAKE:
			{
				HandshakeMessage message = (HandshakeMessage)aMsg;

				JSONArray translators = new JSONArray();
				for ( String translatorName : message.getTranslators() )
					translators.put( translatorName );
				
				jsonMessage.put( SUPPORTED_TRANSLATORS, translators );

				jsonMessage.put( ID, message.getSender().getID() );
			}break;
			default: throw new JSONException( "UNKNOWN MESSAGE TYPE: " + aMsg.getType() );
			}
		}
		catch ( JSONException aException )
		{
			throw new TranslationException( aException.getMessage() );
		}

		return jsonMessage.toString();
	}

	/**
	 * Translates the given JSON message into a OsamiServiceReference.
	 * 
	 * @param aObject
	 * @return Translated OsamiServiceReference
	 */
	private OsamiServiceReference JSONToServiceReference( JSONObject aObject )
	{
		OsamiServiceReference reference = new OsamiServiceReference();

		try
		{
			reference.setName( aObject.getString( SERVICE_NAME ) );
			reference.setInterface( aObject.getString( SERVICE_INTERFACES ) );

			Vector<OsamiIdentifier> routeToService = new Vector<OsamiIdentifier>();
			JSONArray route = aObject.getJSONArray( ROUTE_TO_SERVICE );
			for ( int i = 0; i < route.length(); i++ )
				routeToService.add( new OsamiIdentifier( route.getInt(i) ) );
			reference.setRoute( routeToService );
		}
		catch ( JSONException exception )
		{
			exception.printStackTrace();
		}
		
		return reference;
	}
	
	/**
	 * Translates the given OsamiServiceReference into a JSON message.
	 * 
	 * @param aReference
	 * @return Translated JSONObject
	 */
	private JSONObject ServiceReferenceToJSON( OsamiServiceReference aReference )
	{
		JSONObject serviceJSON = new JSONObject();

		try
		{
			serviceJSON.put( SERVICE_NAME, aReference.getName() );
			serviceJSON.put( SERVICE_INTERFACES, aReference.getInterface() );

			// create a json-array from the route and insert it to the message 
			JSONArray route = new JSONArray();
			for ( OsamiIdentifier identifier : aReference.getRoute() )
				route.put( identifier.getID() );
			serviceJSON.put( ROUTE_TO_SERVICE, route );
		}
		catch ( JSONException exception )
		{
			exception.printStackTrace();
		}
		
		return serviceJSON;
	}
}
